/*
 * Copyright (c) 2010, German Buela
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * The name of German Buela may not be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY GERMAN BUELA "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL GERMAN BUELA BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.gbuela.winco.model

import java.io.{File, FileFilter}
import com.gbuela.file.FileUtil

class Track (val title:String, val time:String, val fileName: String, val location:Option[String])
extends Cloneable
{  
  if(title == null) throw new IllegalArgumentException("Track must have title")
  if(fileName == null) throw new IllegalArgumentException("Track must have fileName")
  if(location == null) throw new IllegalArgumentException("Track must have location")
  
  override def toString = if(time == null || time.length == 0) title
                          else title + " (" + time + ")"
  
  override def clone:AnyRef = new Track(title, time, fileName, location)
  
  def withLocation(newLoc: String) = new Track(title, time, fileName, Some(newLoc))
  
  def toXml = (<track><title>{ title }</title><file>{ fileName }</file><location>{ location getOrElse "" }</location></track>)
}

trait SubnodesResolver[A] {
  val location:String
  def resolveSubnodes:List[A]
}

trait SwincoPlaylistWriter {
	def writeSwincoPlaylist(list: List[Track]): Unit
}

trait TracksResolver extends SubnodesResolver[Track] with SwincoPlaylistWriter {
  import scala.xml.{XML,Node}
  
  val audioExtensions: List[String]
  
  def resolveSubnodes:List[Track] = {
    val playlistFilename = FileUtil.firstFileWithExtension(new File(location), Array(".pl.xml"))
    
    playlistFilename match {
      case Some(filename) =>
	      val content = XML.loadFile(new File(filename))
	      val tracks = content \ "tracks" \ "track"
	      val playlist = tracks map { x:Node =>
	        val title = (x \ "title").text
	        val file = (x \ "file").text
	        val location: Option[String] =  (x \ "location").text match { 
	          case null => None
	          case loc if loc.length > 0 => Some(loc) 
	          case _ => None
	        }
	        new Track(title, "", file, location)
	      }
	      playlist.toList
      case None =>
	       import com.gbuela.winco.system._
	       val dir = new File(location)
	       val ff = new FileFilter {
	         def accept(pathname:File) = (audioExtensions find { pathname.getName.toLowerCase endsWith _ }) != None 
	       }
	       val files = dir listFiles ff
	       val playlist: List[Track] = files match {
	         case null => Nil
	         case _ => (files map { file =>  new Track(file.getName, "", file.getName, None) }).toList
	       }
	       writeSwincoPlaylist(playlist)
	       playlist
    }
  }
}

trait ImageInfo {
  val mainFilename: String
  val altFilename: Option[String]
  val thumbFilename: String
  val isBigImage: Boolean
}

class CustomPlaylistImageInfo extends ImageInfo {
  val mainFilename: String = "./swinco-cp.jpg"
  val altFilename: Option[String] = None
  val thumbFilename: String = mainFilename
  val isBigImage: Boolean = false
}

trait ImageInfoResolver {
  
  val location:String
  
  lazy val dir = new File(location)
  
  lazy val smallImgName:String = {
	  val i = new File(dir, mainImgDefaultName)
	  if(i.exists)
	  {
	    i.getAbsolutePath
	  }
	  else
	  {
	    val firstJpg = FileUtil.firstFileWithExtension(dir, Array(".jpg", ".jpeg"))
        firstJpg getOrElse "./swinco.jpg"
	  }
  } 
  
  val bigImgDefaultName = "ab_cover2.jpg"
  val mainImgDefaultName = "ab_cover.jpg"
  val altImgName = "alt.jpg"

  def resolveMain = {
	var i = new File(dir, bigImgDefaultName)
	if(i.exists)
	{
	  i.getAbsolutePath
	}
    else 
    {
      smallImgName
    }
  }
  
  def resolveAlt = {
    val i = new File(dir, altImgName)
    if(i.exists) Some(i.getAbsolutePath) else None    
  }

  def resolveImageInfo:ImageInfo = new ImageInfo {
    lazy val mainFilename: String = resolveMain
    lazy val altFilename: Option[String] = resolveAlt
    lazy val thumbFilename: String = smallImgName
    lazy val isBigImage: Boolean = mainFilename endsWith bigImgDefaultName
  }
}

abstract class AbstractAlbum extends Comparable[Album] with SwincoPlaylistWriter
{
  val artistName: String
  val title: String
  val year: Option[Int]
  val location: String
  val pathResolver: String => String
  var tracks: List[Track]
  val imageInfo: ImageInfo
  
  def compareTo(other:Album) = other.year match {
    case Some(yo) => this.year match {
      case Some(yt) => (new java.lang.Integer(yt)) compareTo (new java.lang.Integer(yo))
      case _ => title compareTo other.title
    }
    case _ => title compareTo other.title
  } 
  
  def setTracks(newTracks: List[Track])  = tracks = newTracks

}
class Album (val artistName:String, val title:String, val year:Option[Int], 
             val location:String, val pathResolver: String => String, val audioExtensions: List[String])
extends AbstractAlbum with ImageInfoResolver with TracksResolver 
{
  if(artistName == null) throw new IllegalArgumentException("Album must have artist name")
  if(title == null) throw new IllegalArgumentException("Album must have title")
  if(location == null) throw new IllegalArgumentException("Album must have location")

  var tracks = resolveSubnodes
  lazy val imageInfo = resolveImageInfo

    def writeSwincoPlaylist(list: List[Track]): Unit = {
      import scala.xml.XML
      val xml = (<playlist><tracks>{ list map (x => x.toXml) }</tracks></playlist>)
      XML.save(location + "/playlist.pl.xml", xml, "UTF-8")
  }
    
    def reloadSwincoPlaylist: Unit = {
    	  val playlistFilename = FileUtil.firstFileWithExtension(new File(location), Array(".pl.xml"))
    	  
		    if(playlistFilename.isDefined)
		    {
		      val plFile = new File(playlistFilename.get)
               plFile.delete
               tracks = resolveSubnodes
            }
    }
}

class CustomAlbum(var tracks: List[Track], val pathResolver: String => String) extends AbstractAlbum 
 {
  val artistName: String = ""
  val title: String = "Custom Playlist"
  val year: Option[Int] = None
  val location: String = "$"
  lazy val imageInfo = new CustomPlaylistImageInfo
  
  def addingTracks(newTracks: List[Track], location: String) = new CustomAlbum(tracks ::: (newTracks map (_.withLocation(location))), pathResolver)
  def removingTracks(oldTracks: List[Track]) = new CustomAlbum(tracks filter (!oldTracks.contains(_)), pathResolver)
  
  def writeSwincoPlaylist(list: List[Track]): Unit = ()
 }
 
trait ModelFolderResolver[T] extends SubnodesResolver[T] {
  
  val pathResolver: String => String
  
  def modelFromFolder(f:File):T
  
  def resolveSubnodes:List[T] = {
	  val dir = new File(pathResolver(location))
	  val ff = new FileFilter {
	    def accept(pathname:File) = pathname isDirectory 
	  }
	  val files = dir listFiles ff
	  val elems = files map modelFromFolder
      elems.toList
    }
}

trait AlbumFilenameDecomposer {
   def decomposeFilename(fn:String, defArtist:String):(String,Option[Int],String) = {
     val fn2 = fn.replace("__", "~~")
     
     val a = fn2 split "_"
     
     val (y,b) = if(a.size == 1) (None,a(0)) else (Some(a(0).toInt),a(1))
     
     val a2 = b split "~~"

     val (art,tit) = if(a2.size == 1) (defArtist,a2(0)) else (a2(0),a2(1))
     
    (art, y, tit)
  }
}

trait AlbumsResolver extends ModelFolderResolver[Album] with AlbumFilenameDecomposer {
  val audioExtensions: List[String]
  
  def modelFromFolder(f:File):Album = {
    val (artist, yearOpt, title) = decomposeFilename(f.getName, f.getParentFile.getName)
    new Album(artist, title, yearOpt, f.getAbsolutePath, pathResolver, audioExtensions)
  }
}

trait ArtistsResolver extends ModelFolderResolver[Artist] {
  val audioExtensions: List[String]
  def modelFromFolder(f:File):Artist = new Artist(f.getName, f.getAbsolutePath, pathResolver, audioExtensions)
}

class Artist (val name:String, val location:String, val pathResolver: String => String, val audioExtensions: List[String])
extends Comparable[Artist] with AlbumsResolver
{
    lazy val albums = resolveSubnodes
	def compareTo(other:Artist) = name compareTo other.name

	override def equals(other:Any) = other match {
	  case null => false
      case x:Artist => name == x.name && location == x.location
      case _ => false
	}
    override def hashCode = name.length + location.length
}

class Musicollection (val name:String, val location:String, val pathResolver: String => String, val audioExtensions: List[String])
extends Comparable[Musicollection] with ArtistsResolver
{
    lazy val artists = resolveSubnodes
  
    def compareTo(other:Musicollection) = name compareTo other.name
    
    override def equals(other:Any) = other match {
      case null => false
      case x:Musicollection => name == x.name && location == x.location
      case _ => false
    }
    override def hashCode = name.length + location.length
}